package com.javaking.clanteam.studentutils.courses;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;

import com.javaking.clanteam.calendar.provider.CalendarContract.Events;
import com.javaking.clanteam.studentutils.sql.CourseHelper;
import com.javaking.clanteam.studentutils.sql.MissingPropertyException;
import com.javaking.clanteam.studentutils.utils.DatePair;


public class CourseData implements Parcelable {
	
	private Map<String,String> mData;
	
	@SuppressLint("SimpleDateFormat")
	public static final DateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm E");
	
	private long mCreateTime = -1;
	
	// Cannot instantiate except the builder
	private CourseData() {
		mData = new HashMap<String, String>();
		mCreateTime = Calendar.getInstance().getTimeInMillis();
	}
	
	/**
	 * @param source
	 */
	@SuppressWarnings("unchecked")
	private CourseData(Parcel source) {
		mData.putAll(source.readHashMap(null));
	}

	public String getCourseTitle() {
		return mData.get(CourseHelper.COLUMN_COURSE_TITLE);
	}
	
	public String getTeacher() {
		return mData.get(CourseHelper.COLUMN_TEACHER);
	}
	
	public String getRoom() {
		return mData.get(CourseHelper.COLUMN_ROOM);
	}
	
	public String getId() {
		return mData.get(CourseHelper.COLUMN_ID);
	}
	
	/**
	 * Set the id of this course. ONLY <code>CourseHelper</code> SHOULD USE THIS.
	 * NOTE: This method can only be called once. Any subsequent calls will throw
	 * 	an <code>IllegalAccessException</code>.
	 * 
	 * @hide
	 * @param id
	 * @throws IllegalAccessException If you attempt to set the ID once it's already set
	 */
	public void setId(String id) throws IllegalAccessException {
		if (mData.get(CourseHelper.COLUMN_ID)!=null) {
			throw new IllegalAccessException();
		}
		mData.put(CourseHelper.COLUMN_ID, id);
	}
	
	public List<DatePair> getTimes() throws IllegalStateException {
		String arrayString = mData.get(CourseHelper.COLUMN_TIMES);
		
		if (arrayString.equals("-404")) {
			return null;
		}
		
		String[] timeString = arrayString.split(",");
		
		DatePair[] times = new DatePair[timeString.length/2];
		
		Calendar createRef = Calendar.getInstance();
		Calendar tempCal = Calendar.getInstance();
		if (mCreateTime!=-1) {
			createRef.setTimeInMillis(mCreateTime);
		}
		
		for (int i = 0; i < timeString.length; i+=2) {
			try {
				tempCal.setTime(DATE_FORMAT.parse(timeString[i]));
				tempCal.set(Calendar.MONTH, createRef.get(Calendar.MONTH));
				tempCal.set(Calendar.YEAR, createRef.get(Calendar.YEAR));
				Date start = tempCal.getTime();
				tempCal.setTime(DATE_FORMAT.parse(timeString[i+1]));
				tempCal.set(Calendar.MONTH, createRef.get(Calendar.MONTH));
				tempCal.set(Calendar.YEAR, createRef.get(Calendar.YEAR));
				Date end = tempCal.getTime();
				times[i/2] = new DatePair(start,end);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		return Arrays.asList(times);
	}
	
	public int getPeriod() {
		int period = Integer.parseInt(mData.get(CourseHelper.COLUMN_PERIOD));
		if (period==-404) {
			throw new IllegalStateException("This course does not have a period " +
					"associated with it.");
		}
		return period;
	}
	
	/**
	 * Constructs a new <tt>Map</tt> with the contents stored in this
	 * <tt>course</tt> placed into the map
	 * 
	 * @return
	 */
	public Map<String,String> getCopyOfMap() {
		HashMap<String, String> newMap = new HashMap<String,String>();
		newMap.putAll(mData);
		return newMap;
	}
	
	/** 
	 * 
	 * Parse the first row of <tt>cursor</tt> into a course object. If the cursor
	 * is empty, null is returned. Any rows other than the first are ignored.
	 * 
	 * @param cursor The cursor to retrieve the course from
	 * @return The first row in <tt>cursor</tt>
	 */
	public static CourseData getCourseFromCursor(Cursor cursor) {
		CourseData course = new CourseData();
		if (!cursor.moveToFirst()) {
			return null;
		}
		
		final int columnCount = cursor.getColumnCount();
		
		// Three of the columns are required so I'm going the lazy route and just
		// checking to see if we have at least three columns.
		if (columnCount<3) {
			return null;
		}
		
		for(int i = 0; i < columnCount; i++) {
			course.mData.put(cursor.getColumnName(i), cursor.getString(i));
		}
		return course;
	}
	
	public static CourseData buildCourseFromMap(Map<String,String> map) {
		CourseData data = new CourseData();
		data.mData.putAll(map);
		return data;
	}
	
	public static CourseData[] getAllCourseFromCursor(Cursor cursor) {
		List<CourseData> courseList = new ArrayList<CourseData>();
		
		while (cursor.moveToNext()) {
			
			CourseData course = new CourseData();
			
			final int columnCount = cursor.getColumnCount();
			for(int i = 0; i < columnCount; i++) {
				course.mData.put(cursor.getColumnName(i), cursor.getString(i));
			}
			
			courseList.add(course);
		}
		return courseList.toArray(new CourseData[0]);
	}
	
	public ContentValues convertToContentValues() {
		ContentValues content = new ContentValues();
		for (Iterator<String> iterator = mData.keySet().iterator(); iterator.hasNext();) {
			String key = iterator.next();
			content.put(key, mData.get(key));
		}
		
		// Some clean-up before we return the end result.
		if (mCreateTime!=-1) {
			content.put(CourseHelper.COLUMN_CREATE_DATE, mCreateTime);
		} else {
			content.remove(CourseHelper.COLUMN_CREATE_DATE);
		}
		content.remove(CourseHelper.COLUMN_ID);
		return content;
	}
	
	/**
	 * Converts a course into a content values that will be acceptable for 
	 * the provider to insert into the calendar.
	 * 
	 * @param course The course to convert
	 * @return a contentValues containing timing information for this course.
	 */
	public static ContentValues courseToEventValues(CourseData course) {
		return courseToEventValues(course, "1");
	}
	
	/**
	 * Converts a course into a content values that will be acceptable for 
	 * the provider to insert into the calendar.
	 * 
	 * @param course The course to convert
	 * @param rrule 
	 * @return a contentValues containing timing information for this course.
	 */
	public static ContentValues courseToEventValues(CourseData course, String calendarID) {
		ContentValues cv = new ContentValues();
		cv.put(Events.CALENDAR_ID, calendarID);
		cv.put(Events.TITLE, course.getCourseTitle());
		cv.put(Events.EVENT_LOCATION, "Room: "+course.getRoom());
		cv.put(Events.EVENT_TIMEZONE, Calendar.getInstance().getTimeZone().getDisplayName());
		for (DatePair pair: course.getTimes()) {
			cv.put(Events.START_TIME, pair.getStartDate().getTime());
			cv.put(Events.END_TIME, pair.getEndDate().getTime());
		}
		return cv;
	}

	@SuppressLint("SimpleDateFormat")
	public static class Builder {
		
		private CourseData mCourse;
		
		public Builder() {
			mCourse = new CourseData();
			
			mCourse.mData.put(CourseHelper.COLUMN_PERIOD, "-404");
			mCourse.mData.put(CourseHelper.COLUMN_TIMES, "-404");
		}
		
		public void setCourseTitle(String course) {
			mCourse.mData.put(CourseHelper.COLUMN_COURSE_TITLE, course);
		}
		
		public void setTeacher(String teacher) {
			mCourse.mData.put(CourseHelper.COLUMN_TEACHER, teacher);
		}
		
		public void setRoom(String room) {
			mCourse.mData.put(CourseHelper.COLUMN_ROOM, room);
		}
		
		public void setTimes(Date[] times) {
			if ((times.length%2)!=0) {
				throw new IllegalArgumentException("Times must have an even number" +
						" of elements: start and end times.");
			}
			
			String[] convertedTimes = new String[times.length];
			for (int i = 0; i<times.length; i++) {
				convertedTimes[i] = CourseData.DATE_FORMAT.format(times[i]);
			}
			
			String arrayString = Arrays.toString(convertedTimes);
			arrayString = arrayString.substring(1, arrayString.length()-1);
			
			mCourse.mData.put(CourseHelper.COLUMN_TIMES, arrayString);
		}
		
		public void setPeriod(int period) {
			mCourse.mData.put(CourseHelper.COLUMN_PERIOD, String.valueOf(period));
		}
		
		/**
		 * 
		 * @return The generated course
		 * 
		 *  @throws MissingPropertyException if you have not set the course name
		 */
		public CourseData build() {
			if (mCourse.mData.containsKey(CourseHelper.COLUMN_COURSE_TITLE)) {
				return mCourse;
			} else {
				throw new MissingPropertyException("course name not set");
			}
		}

		public void addTime(DatePair pair) {
			String[] timeStrings = mCourse.mData.get(CourseHelper.COLUMN_TIMES).split(",");
			int length = timeStrings.length;
			if (length==1&&timeStrings[0].equals("-404")) {
				timeStrings = new String[0];
				length = 0;
			}
			String[] newTimeStrings = new String[length+2];
			System.arraycopy(timeStrings, 0, newTimeStrings, 0, length);
			newTimeStrings[length] = CourseData.DATE_FORMAT.format(pair.getStartDate());
			newTimeStrings[length+1] = CourseData.DATE_FORMAT.format(pair.getEndDate());
			
			String arrayString = Arrays.toString(newTimeStrings);
			arrayString = arrayString.substring(1, arrayString.length()-1);
			
			mCourse.mData.put(CourseHelper.COLUMN_TIMES, arrayString);
		}
		
	}

	/* (non-Javadoc)
	 * @see android.os.Parcelable#describeContents()
	 */
	@Override
	public int describeContents() {
		return 0;
	}

	/* (non-Javadoc)
	 * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
	 */
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeMap(mData);
	}
	
	public static final Parcelable.Creator<CourseData> CREATOR = new Parcelable.Creator<CourseData>() {

		@Override
		public CourseData createFromParcel(Parcel source) {
			return new CourseData(source);
		}

		@Override
		public CourseData[] newArray(int size) {
			return new CourseData[size];
		}
		
	};
	
	public String toString() {
		String string = mData.toString();
		return string.substring(1,string.length()-2);
	};
	
	@Override
	public boolean equals(Object o) {
		if (o instanceof Map<?,?>) {
			return mData.equals(o);
		} else if (o instanceof CourseData) {
			return ((CourseData)o).mData.equals(this.mData);
		} else {
			return super.equals(o);
		}
	}

	
}
